#ifndef EPUBZLIB_COMM_MUTEX_HPP
#define EPUBZLIB_COMM_MUTEX_HPP

SJR_NAMESPACE_BEGIN

class EpubMutex final {
public:
  EpubMutex() : mMutex() {}

  EpubMutex(EpubMutex const &) = delete;
  EpubMutex &operator=(EpubMutex const &) = delete;

  void lock() {
    mMutex.lock();
  }

  void unlock() {
    mMutex.unlock();
  }

private:
  std::mutex mMutex;
};

class EpubUniqueLock final {
public:
  explicit EpubUniqueLock(EpubMutex &mutex) : mMark(), mMutex(mutex) {
    mMark = true;
    mMutex.lock();
  }

  ~EpubUniqueLock() {
    if (mMark)
      mMutex.unlock();
  }

  EpubUniqueLock(EpubUniqueLock const &) = delete;
  EpubUniqueLock &operator=(EpubUniqueLock const &) = delete;

private:
  bool mMark;
  EpubMutex &mMutex;
};

class EpubConditional final {
public:
  constexpr static int COUNT = 1;

  explicit EpubConditional(int count = COUNT)
      : mStep(), mCount(count), mMutex(), mCond(), mSync() {}

  EpubConditional(EpubConditional const &) = delete;
  EpubConditional &operator=(EpubConditional const &) = delete;

  void reset() {
    EpubUniqueLock lock(mSync);
    mStep = 0;
    mCond.notify_one();
  }

  void add(int step = COUNT) {
    EpubUniqueLock lock(mSync);
    mStep += step;
    mCond.notify_one();
  }

  void wait() {
    std::unique_lock<std::mutex> lock(mMutex);
    while (mStep < mCount)
      mCond.wait(lock);
  }

  template<class Fun>
  void wait(Fun &&fun) {
    std::unique_lock<std::mutex> lock(mMutex);
    mCond.wait(fun);
  }

private:
  int mStep;
  int mCount;
  std::mutex mMutex;
  std::condition_variable mCond;
  EpubMutex mSync;
};

SJR_NAMESPACE_END

#endif//EPUBZLIB_COMM_MUTEX_HPP
